#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <inttypes.h>
#include <sys/types.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>

#include "raw_socket.h"

static uint16_t checksum(char *buf, int len)
{
	uint32_t sum = 0;
	int i = 0, k = len/2;
	uint16_t*  tmp = (uint16_t*)buf;
	for (i = 0;i < k; i++)
	{
		sum += *tmp;
		tmp++;
	}
	if (len%2) {
		sum += (uint8_t)(*(buf+len-1));
	}
	do
	{
		sum = ((sum >> 16) + (sum & 0xFFFF));
	} while (sum & 0xFFFF0000);

	return (~sum);
}

uint16_t get_data_offset()
{
	uint16_t eth_len = sizeof(struct ethhdr);
	uint16_t iph_len = sizeof(struct iphdr);
	uint16_t uh_len = sizeof(struct udphdr);
	return (eth_len+iph_len+uh_len);
}

static int get_mac(const char* name, uint8_t *mac)
{
	static struct ifreq ifr;
	int fd, i, r;
	if (strcmp(ifr.ifr_name, name))
	{
		strcpy(ifr.ifr_name, name);
		fd = socket(AF_INET, SOCK_STREAM, 0);
		if (fd < 0) return -2;
		r = ioctl(fd, SIOCGIFHWADDR, &ifr);
		if (r < 0) return -3;
		close(fd);
	}
	for (i = 0;i < 6; i++) {
		mac[i] = ifr.ifr_hwaddr.sa_data[i];
	}
	return 0;
}

static int get_ip(const char* name, struct sockaddr_in* sin)
{
	static struct ifreq ifr;
	int fd, r;
	if (strcmp(ifr.ifr_name, name))
	{
		strcpy(ifr.ifr_name, name);
		fd = socket(AF_INET, SOCK_STREAM, 0);
		if (fd < 0) return -2;
		r = ioctl(fd, SIOCGIFADDR, &ifr);
		if (r < 0) return -3;
		close(fd);
		memcpy(sin, &ifr.ifr_addr, sizeof(struct sockaddr_in));
	}
	return 0;
}

uint16_t init_raw_socket(p_socket_io sock)
{
	unsigned index = 0;
	int _fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
	//int _fd = socket(PF_PACKET, SOCK_RAW, IPPROTO_UDP);
	if ( _fd < 0) {
		printf("error in socket\n");
		return ERR_SOCKET_CREATE;
	}
	sock->fd = _fd;
	memset(&(sock->if_req), 0, sizeof(sock->if_req));
	strncpy(sock->if_req.ifr_name, sock->if_name, IFNAMSIZ - 1);
	index = if_nametoindex(sock->if_name);
	if (index < 0) {
		printf("get eth index failed\n");
		close(sock->fd);
		return ERR_SOCKET_CREATE;
	} 
	if (get_mac(sock->if_name, sock->r_local_addr.sll_addr) < 0) { 
		printf("get eth mac failed:%s\n", sock->if_name);
		close(sock->fd);
		return ERR_SOCKET_CREATE;
	}
	if (get_ip(sock->if_name, &(sock->local_addr)) < 0) { 
		printf("get eth ip failed:%s\n", sock->if_name);
		close(sock->fd);
		return ERR_SOCKET_CREATE;
	}

	sock->r_local_addr.sll_ifindex = index;
	sock->r_local_addr.sll_halen = ETH_ALEN;
	sock->r_remote_addr.sll_ifindex = index;
	sock->r_remote_addr.sll_halen = ETH_ALEN;
	
	sock->r_local_addr.sll_family = AF_PACKET;
	sock->r_local_addr.sll_protocol = htons(ETH_P_ALL);
	if (bind(sock->fd, (struct sockaddr*)&(sock->r_local_addr), sizeof(struct sockaddr_ll)) == -1) {
		return ERR_SOCKET_BIND;
	}
	return NO_ERR;
}

uint16_t raw_back_pre_buffer(char* buf, uint16_t size)
{
	if (size < 64) return ERR_MAIN_PRE_BUF;
	uint16_t eth_len = sizeof(struct ethhdr);
	uint16_t iph_len = sizeof(struct iphdr);
	struct ethhdr *eth = (struct ethhdr *) (buf);
	struct iphdr *iph = (struct iphdr *) (buf + eth_len);
	struct udphdr *uh = (struct udphdr *) (buf + eth_len + iph_len);
	uint8_t tmp_mac[6];
	for (int i = 0;i < 6; i++) {
		tmp_mac[i] = eth->h_source[i];
	}
	for (int i = 0;i < 6; i++) {
		eth->h_source[i] = eth->h_dest[i];
	}
	for (int i = 0;i < 6; i++) {
		eth->h_dest[i] = tmp_mac[i];
	}
	
	uint32_t tmp_addr = iph->saddr;
	iph->saddr = iph->daddr;
	iph->daddr = tmp_addr;
	iph->tot_len = htons(size - eth_len);

	uint16_t tmp_port = uh->source;
	uh->source = uh->dest;
	uh->dest = tmp_port;
	uh->check = 0;
	uh->len = htons(size - eth_len - iph_len);

	iph->check = checksum(buf + eth_len, iph->tot_len);
	return 0;
}

uint16_t raw_pre_send_buffer(p_socket_io sock, char* buf, uint16_t size)
{
	if (size < 64) return ERR_MAIN_PRE_BUF;
	uint16_t eth_len = sizeof(struct ethhdr);
	uint16_t iph_len = sizeof(struct iphdr);
	//uint16_t uh_len = sizeof(struct udphdr);
	struct ethhdr *eth = (struct ethhdr *) (buf);
	struct iphdr *iph = (struct iphdr *) (buf + eth_len);
	struct udphdr *uh = (struct udphdr *) (buf + eth_len + iph_len);
	for (int i = 0;i < 6; i++) {
		eth->h_source[i] = sock->r_local_addr.sll_addr[i];
	}
	for (int i = 0;i < 6; i++) {
		eth->h_dest[i] = sock->r_remote_addr.sll_addr[i];
	}

	eth->h_proto = htons(ETH_P_IP);

	iph->ihl = 5;
	iph->version = 4;
	iph->tos = 0;
	iph->id = htons(10201);
	iph->ttl = 64;
	iph->protocol = IPPROTO_UDP;
	iph->saddr = sock->local_addr.sin_addr.s_addr;
	iph->daddr = sock->remote_addr.sin_addr.s_addr;
	iph->tot_len = htons(size - eth_len);

	uh->source = htons(sock->local_addr.sin_port);
	uh->dest = htons(sock->remote_addr.sin_port);
	uh->check = 0;
	uh->len = htons(size - eth_len - iph_len);

	iph->check = 0;
	iph->check = checksum(buf + eth_len, size - eth_len);

	return 0;
}

uint16_t raw_send_msg(p_socket_io sock, char* buf, uint16_t* size)
{
	int _size = 0;
	int socklen = sizeof(sock->r_remote_addr);
	_size = sendto(sock->fd, buf, *size, 0, (const struct sockaddr *)&(sock->r_remote_addr), socklen);
	if (_size != (*size)){
		return ERR_SEND;
	}
	return NO_ERR;
}

uint16_t raw_recv_msg(p_socket_io sock, char* buf, uint16_t* size)
{
	int _size = 0;
	socklen_t socklen = sizeof(sock->r_remote_addr);
	_size = recvfrom(sock->fd, buf, *size, 0, (struct sockaddr *)&(sock->r_local_addr), &socklen);
	*size = _size;
	return NO_ERR;
}
